import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadClient {

	public static long start_time = System.currentTimeMillis();

	public static void main(String args[]) throws IOException {
		byte[] aByte = new byte[1];
		Socket clientSocket = null;
		DataInputStream is = null;
		String sentence = null;
		int option = 0;
		int maxTcpConnections = 0;
		int port_number=0;
		String Server_IP= null;
		//input from user
		while (true) {                                                                           //port number 
			   System.out.println("CLIENT SIDE");
			   System.out.println("Enter the port number of Server");
			   BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));  
			   port_number = Integer.parseInt(reader.readLine());
			   System.out.println("Now using port number ="+port_number);
			   if (	port_number < 65535 || port_number > 0)
			      break;
			   else
			      System.out.println("Invalid port number. Choose from 0 - 65535");
			}
		while (true) { 																			//option number
			   System.out.println("Enter the option number for TCP/IP connection: ");
			   System.out.println("OPTION-1 :Non-persistent without parallel connections");
			   System.out.println("OPTION-2 :Non-persistent with parallel connections");
			   System.out.println("OPTION-3 :Persistent without pipelining");
			   System.out.println("OPTION-4 :Persistent with pipelining");
			   BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));  
			   option = Integer.parseInt(reader.readLine());
			   System.out.println("Now using option number ="+option);
			   if (	option < 5 || option > 0)
			      break;
			   else
			      System.out.println("Invalid option number. Choose from 1 -4");
			}
		
		while (true) {
			if (option == 1){
				   maxTcpConnections =1;
				   break;}
			else 
				{																			//the maximum number of TCP/IP connection: 
			   System.out.println("Enter the maximum number of TCP/IP connection: ");
			   BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));  
			   maxTcpConnections = Integer.parseInt(reader.readLine());
			   System.out.println("Max no of TCP/IP connection ="+maxTcpConnections);
			   if (	maxTcpConnections > 0)
			      break;
			   else
			      System.out.println("Invalid number");
			   
				}}
			   System.out.println("Max no of TCP/IP connection ="+maxTcpConnections);
		
			
		while (true) {																			// IP address of server
			   System.out.println("Enter the IP address of server");
			   BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));  
			   Server_IP = reader.readLine();
			   System.out.println("Connecting to server with IP address ="+Server_IP);
			   if (	maxTcpConnections > 0)
			      break;
			   else
			      System.out.println("Invalid number");
			}

		
		try {
			clientSocket = new Socket(Server_IP, port_number);              //open client socket at port number and server IP
			DataOutputStream outToServer = new DataOutputStream(
					clientSocket.getOutputStream());
			sentence = "Default";											//Requesting for fileList.txt
			outToServer.writeBytes(sentence + '\n');
			ByteArrayOutputStream baos = new ByteArrayOutputStream();       // open new ByteArrayOutputStream 
			is = new DataInputStream(clientSocket.getInputStream());		//new DataInputStream
			if (is != null) {
				FileOutputStream fos = null;
				BufferedOutputStream bos = null;
				fos = new FileOutputStream(
						"C:\\Users\\Arunabh\\workspace\\CCN2\\src\\Received\\filelistop.txt");   
				bos = new BufferedOutputStream(fos);                            
				
				long length = is.readLong();
				System.out.println(length);
				for (long i = 0; i < length; i++) { 				//reading contents of file
					is.read(aByte);
					baos.write(aByte);
				}
				bos.write(baos.toByteArray());						//writing to byte array 
				bos.flush();
				bos.close();
				System.out.println("File copied successfully...");   //fileList.txt received
				if (!(option == 3 || option == 4)) {
					clientSocket.close();
					readFile(option,maxTcpConnections,port_number,Server_IP); // method to parse and read filelist.txt
				} else {
					readFilePersistence(option, clientSocket, is, outToServer); // read file for option 3 and 4
				}
			}
		} catch (Exception ex) {
			System.err.println(ex.getMessage());
		}
		System.out.println("TOTAL TIME TAKEN BY CLIENT = "+(System.currentTimeMillis()-start_time)+"msec");

	}

	//method to readFile
	public static void readFile(int option, int maxTcpConnections,int port_number,String Server_IP) {      
		try {
		
			FileInputStream fstream = new FileInputStream(
					"C:\\Users\\Arunabh\\workspace\\CCN2\\src\\Received\\FileListop.txt");
			// Get the object of DataInputStream
			DataInputStream in = new DataInputStream(fstream);
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
			String strLine;
			// Read File Line By Line
			int i = 1;
			ThreadPoolExecutor executor=null;				//define thread pool executor 
			if(option ==2){
			executor = new ThreadPoolExecutor(
					maxTcpConnections,maxTcpConnections,1000,TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(10));}   // set thread pool executor
			while ((strLine = br.readLine()) != null) {
				System.out.println("reading file" + i);
				i++;
				if (option == 1) {
					C_Thread c_thread = new C_Thread(strLine, new Socket(
							Server_IP, port_number), option);                    //create thread
					c_thread.run();
				} else if (option == 2) { 
					C_Thread c_thread = new C_Thread(strLine, new Socket(       //create and execute thread from executor
							Server_IP, port_number), option);                          
					executor.execute(c_thread);
				}
			}
			if(option == 2)
			{
				executor.shutdown();                       //close executor 
				while(!executor.isTerminated()){
					//do nothing
				}
			}
			// Close the input stream
			in.close();
		} catch (Exception e) {// Catch exception if any
			System.err.println("Error: " + e.getMessage());
		}
	}

	//readfile method for option 3 and option 4
	public static void readFilePersistence(int option, Socket clientSocket,
			DataInputStream is1, DataOutputStream outToServer1) {
		try {
			FileInputStream fstream = new FileInputStream(
					"C:\\Users\\Arunabh\\workspace\\CCN2\\src\\Received\\FileListop.txt");
			FileInputStream fstream1 = new FileInputStream(
					"C:\\Users\\Arunabh\\workspace\\CCN2\\src\\Received\\FileListop.txt");
			DataInputStream in = new DataInputStream(fstream);
			DataInputStream in1= new DataInputStream(fstream1);
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
			String strLine;
			// Read File Line By Line
			int i = 1;
			if(option == 3)
			{
				while ((strLine = br.readLine()) != null) {
					System.out.println("reading file" + i);
					i++;
				C_Thread c_thread = new C_Thread(strLine, clientSocket, is1,
						outToServer1, option); 									//create and run thread while all files have been read
				c_thread.run();
				}
				
				if (!clientSocket.isClosed())
				clientSocket.close();
				in.close();			// Close the input stream
			}
			else if(option == 4)
			{
				BufferedReader br1 = new BufferedReader(new InputStreamReader(in1));
				C_Thread c_thread1 = new C_Thread(br, clientSocket, 
						outToServer1, option);
				Thread t1 = new Thread(c_thread1);
				t1.start();
				C_Thread c_thread = new C_Thread(br1, clientSocket, is1,
						 option);
				Thread t= new Thread(c_thread);
				t.start();
			}
		} catch (Exception e) {// Catch exception if any
			System.err.println("Error: " + e.getMessage());
		}
	}
}

//The Runnable interface implemented by class, C_thread
class C_Thread implements Runnable {
	private String fileName = null;
	private Socket clientSocket1 = null;
	private int option;
	private DataOutputStream outToServer1 = null;
	private DataInputStream is1 = null;
	private BufferedReader br = null;	

	public C_Thread(String fileName, Socket clientSocket, int option) {           //constructor
		this.fileName = fileName;
		this.clientSocket1 = clientSocket;
		this.option = option;
	}

	public C_Thread(String fileName, Socket clientSocket, DataInputStream is1,	//constructor
			DataOutputStream outToServer1, int option) {
		this.fileName = fileName;
		this.clientSocket1 = clientSocket;
		this.option = option;
		this.outToServer1 = outToServer1;
		this.is1 = is1;
	}
	public C_Thread(BufferedReader br, Socket clientSocket,					//constructor
			DataOutputStream outToServer1, int option) {

		this.br = br;
		this.clientSocket1 = clientSocket;
		this.option = option;
		this.outToServer1 = outToServer1;
	}
		
	public C_Thread(BufferedReader br, Socket clientSocket, DataInputStream is1,		//constructor
			 int option) {
		this.br = br;
		this.clientSocket1 = clientSocket;
		this.option = option;
		this.is1 = is1;
	}


	public void run() {
		DataInputStream is2 = null;
		DataOutputStream outToServer2 = null;
		String fileRequested = null;
		try {
			if (this.option == 3 || this.option == 4) {
				is2 = this.is1;
				outToServer2 = this.outToServer1;
			} else {												// for option 1 and option 2 
				outToServer2 = new DataOutputStream(
						this.clientSocket1.getOutputStream());
				is2 = new DataInputStream(this.clientSocket1.getInputStream());
			}
			if(this.option == 4 && outToServer2!=null)
			{
				int i = 1;
				String strLine = null;
				while ((strLine = br.readLine()) != null) {
					System.out.println("reading file" + i);
					i++;
					outToServer2.writeBytes(strLine + '\n');			
					outToServer2.flush();
				}
				br.close();
			}
			else if(outToServer2!=null)
			{
				outToServer2.writeBytes(this.fileName + '\n');
				outToServer2.flush();
			}
			if(this.option == 4 && is2 != null)
			{
				while ((this.br.readLine() != null)&&((fileRequested = is2.readUTF()) != null)){
					
					this.getFile(is2, fileRequested);
				}
				if (!clientSocket1.isClosed())
					this.clientSocket1.close();
				System.out.println("Time taken for file fetch = "+(System.currentTimeMillis()-ThreadClient.start_time)+" msec");
			}
			else if (is2 != null)
			{
				long start = System.currentTimeMillis();
				this.getFile(is2, is2.readUTF());						//call getFile method, and send legth of file as an argument
				System.out.println("Seconds for file fetch = "+(System.currentTimeMillis()-start)+"msec");
			}
			if (!(this.option == 3 || this.option == 4)) {
				
				if (!clientSocket1.isClosed())
					this.clientSocket1.close();
			}
		} catch (Exception ex) {
			System.err.println(ex.getMessage());
		}
	}
	
	//Method: Getfile from server 
	public void getFile(DataInputStream is2, String fileRequested) throws Exception
	{
				FileOutputStream fos1 = null;
				BufferedOutputStream bos1 = null;
				ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
			byte[] aByte1 = new byte[1];	
			if(fileRequested == "0"){
				System.out.print("File does not exists");
			}
			
						else {						//error handling for case when file does not exists 
			fos1 = new FileOutputStream(
					"C:\\Users\\Arunabh\\workspace\\CCN2\\src\\Received\\"
							+ fileRequested);
			bos1 = new BufferedOutputStream(fos1);
			long length = is2.readLong();
			for (long i = 0; i < length; i++) {							//read file 
				is2.read(aByte1);
				baos1.write(aByte1);
			}
			bos1.write(baos1.toByteArray());							//write bytearray 
			bos1.flush();
			bos1.close();
			fos1.close();
			baos1.close();
			System.out.println("File copied successfully..."
					+ fileRequested);}
						
			}
	}

